/*-------------------------------------------------------------------------
 *
 * xlogfuncs.c
 *
 * PostgreSQL 写前日志管理器用户界面函数
 *
 * 此文件包含 WAL 控制和信息功能。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/access/transam/xlogfuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#include "access/htup_details.h"
#include "access/xlog_internal.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/walreceiver.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/pg_lsn.h"
#include "utils/timestamp.h"
#include "utils/tuplestore.h"

/*
 * 在备份过程中存储标签文件和表空间映射。
 */
static StringInfo label_file;
static StringInfo tblspc_map_file;

/*
 * pg_backup_start: 设置以进行在线备份转储
 *
 * 实质上，这个操作是在 $PGDATA 中创建一个备份标签文件，
 * 该文件将作为备份转储的一部分进行归档。标签文件
 * 包含用户提供的标签字符串（通常用于指示备份转储将存储的位置）以及
 * 转储的起始时间和起始 WAL 位置。
 *
 * 对此功能的权限检查通过正常的 GRANT 系统进行管理。
 */
Datum pg_backup_start(PG_FUNCTION_ARGS)
{
	text	   *fc_backupid = PG_GETARG_TEXT_PP(0);
	bool		fc_fast = PG_GETARG_BOOL(1);
	char	   *fc_backupidstr;
	XLogRecPtr	fc_startpoint;
	SessionBackupState fc_status = get_backup_status();
	MemoryContext fc_oldcontext;

	fc_backupidstr = text_to_cstring(fc_backupid);

	if (fc_status == SESSION_BACKUP_RUNNING)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("a backup is already in progress in this session")));

	/*
	 * 标签文件和表空间映射文件需要长寿命，因为它们
	 * 在 pg_backup_stop 中被读取。
	 */
	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);
	label_file = makeStringInfo();
	tblspc_map_file = makeStringInfo();
	MemoryContextSwitchTo(fc_oldcontext);

	register_persistent_abort_backup_handler();

	fc_startpoint = do_pg_backup_start(fc_backupidstr, fc_fast, NULL, label_file,
									NULL, tblspc_map_file);

	PG_RETURN_LSN(fc_startpoint);
}


/*
 * pg_backup_stop: 完成在线备份。
 *
 * 第一个参数（变量 'waitforarchive'）是可选的，
 * 允许用户选择是否等待 WAL 被归档
 * 或者我们是否应该在 WAL 记录写入后立即返回。
 *
 * 对此功能的权限检查通过正常的 GRANT 系统进行管理。
 */
Datum pg_backup_stop(PG_FUNCTION_ARGS)
{
#define PG_STOP_BACKUP_V2_COLS 3
	TupleDesc	fc_tupdesc;
	Datum		fc_values[PG_STOP_BACKUP_V2_COLS];
	bool		fc_nulls[PG_STOP_BACKUP_V2_COLS];

	bool		fc_waitforarchive = PG_GETARG_BOOL(0);
	XLogRecPtr	fc_stoppoint;
	SessionBackupState fc_status = get_backup_status();

	/* 初始化元组描述符中的属性信息 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	if (fc_status != SESSION_BACKUP_RUNNING)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("backup is not in progress"),
				 errhint("Did you call pg_backup_start()?")));

	/*
	 * 停止备份。返回备份标签和表空间映射的副本
	 * 以便调用者可以将其写入磁盘。
	 */
	fc_stoppoint = do_pg_backup_stop(label_file->data, fc_waitforarchive, NULL);

	fc_values[0] = LSNGetDatum(fc_stoppoint);
	fc_values[1] = CStringGetTextDatum(label_file->data);
	fc_values[2] = CStringGetTextDatum(tblspc_map_file->data);

	/* 释放在 TopMemoryContext 中分配的结构 */
	pfree(label_file->data);
	pfree(label_file);
	label_file = NULL;
	pfree(tblspc_map_file->data);
	pfree(tblspc_map_file);
	tblspc_map_file = NULL;

	/* 将记录作为 Datum 返回 */
	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(fc_tupdesc, fc_values, fc_nulls)));
}

/*
 * pg_switch_wal: 切换到下一个 xlog 文件
 *
 * 对此功能的权限检查通过正常的 GRANT 系统进行管理。
 */
Datum pg_switch_wal(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_switchpoint;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("WAL control functions cannot be executed during recovery.")));

	fc_switchpoint = RequestXLogSwitch(false);

	/*
	 * 作为便利，返回切换记录的 WAL 位置
	 */
	PG_RETURN_LSN(fc_switchpoint);
}

/*
 * pg_create_restore_point: 一个用于恢复的命名点
 *
 * 对此功能的权限检查通过正常的 GRANT 系统进行管理。
 */
Datum pg_create_restore_point(PG_FUNCTION_ARGS)
{
	text	   *fc_restore_name = PG_GETARG_TEXT_PP(0);
	char	   *fc_restore_name_str;
	XLogRecPtr	fc_restorepoint;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("WAL control functions cannot be executed during recovery.")));

	if (!XLogIsNeeded())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("WAL level not sufficient for creating a restore point"),
				 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));

	fc_restore_name_str = text_to_cstring(fc_restore_name);

	if (strlen(fc_restore_name_str) >= MAXFNAMELEN)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1)));

	fc_restorepoint = XLogRestorePoint(fc_restore_name_str);

	/*
	 * 作为便利，返回恢复点记录的 WAL 位置
	 */
	PG_RETURN_LSN(fc_restorepoint);
}

/*
 * 报告当前 WAL 写入位置（格式与 pg_backup_start 等相同）
 *
 * 这对于确定外部归档进程可见的 WAL 量是有用的。请注意，此点之前的数据已写入
 * 内核，但不一定已同步到磁盘。
 */
Datum pg_current_wal_lsn(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_current_recptr;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("WAL control functions cannot be executed during recovery.")));

	fc_current_recptr = GetXLogWriteRecPtr();

	PG_RETURN_LSN(fc_current_recptr);
}

/*
 * 报告当前 WAL 插入位置（格式与 pg_backup_start 等相同）
 *
 * 此功能主要用于调试目的。
 */
Datum pg_current_wal_insert_lsn(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_current_recptr;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("WAL control functions cannot be executed during recovery.")));

	fc_current_recptr = GetXLogInsertRecPtr();

	PG_RETURN_LSN(fc_current_recptr);
}

/*
 * 报告当前 WAL 刷新位置（格式与 pg_backup_start 等相同）
 *
 * 此功能主要用于调试目的。
 */
Datum pg_current_wal_flush_lsn(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_current_recptr;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("WAL control functions cannot be executed during recovery.")));

	fc_current_recptr = GetFlushRecPtr(NULL);

	PG_RETURN_LSN(fc_current_recptr);
}

/*
 * 报告最后一个 WAL 接收位置（格式与 pg_backup_start 等相同）
 *
 * 这对于确定 WAL 中保证被接收并已同步到磁盘的部分是有用的
 * 及 walreceiver。
 */
Datum pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_recptr;

	fc_recptr = GetWalRcvFlushRecPtr(NULL, NULL);

	if (fc_recptr == 0)
		PG_RETURN_NULL();

	PG_RETURN_LSN(fc_recptr);
}

/*
 * 报告最后一个 WAL 重放位置（格式与 pg_backup_start 等相同）
 *
 * 这对于确定在恢复期间只读连接可见的 WAL 量是有用的。
 */
Datum pg_last_wal_replay_lsn(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_recptr;

	fc_recptr = GetXLogReplayRecPtr(NULL);

	if (fc_recptr == 0)
		PG_RETURN_NULL();

	PG_RETURN_LSN(fc_recptr);
}

/*
 * 计算给定WAL位置的xlog文件名和十进制字节偏移量，
 * 例如由pg_backup_stop()或pg_switch_wal()返回的。
 *
 * 注意，恰好位于片段边界的位置信息被视为在
 * 之前的片段中。这通常是正确的，因为
 * 预期的用途是确定哪些xlog文件准备进行归档。
 */
Datum pg_walfile_name_offset(PG_FUNCTION_ARGS)
{
	XLogSegNo	fc_xlogsegno;
	uint32		fc_xrecoff;
	XLogRecPtr	fc_locationpoint = PG_GETARG_LSN(0);
	char		fc_xlogfilename[MAXFNAMELEN];
	Datum		fc_values[2];
	bool		fc_isnull[2];
	TupleDesc	fc_resultTupleDesc;
	HeapTuple	fc_resultHeapTuple;
	Datum		fc_result;

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("%s cannot be executed during recovery.",
						 "pg_walfile_name_offset()")));

	/*
	 * 为结果行构造一个元组描述符。必须与此函数的 pg_proc 条目匹配！
	 */
	fc_resultTupleDesc = CreateTemplateTupleDesc(2);
	TupleDescInitEntry(fc_resultTupleDesc, (AttrNumber) 1, "file_name",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_resultTupleDesc, (AttrNumber) 2, "file_offset",
					   INT4OID, -1, 0);

	fc_resultTupleDesc = BlessTupleDesc(fc_resultTupleDesc);

	/*
	 * xlogfilename
	 */
	XLByteToPrevSeg(fc_locationpoint, fc_xlogsegno, wal_segment_size);
	XLogFileName(fc_xlogfilename, GetWALInsertionTimeLine(), fc_xlogsegno,
				 wal_segment_size);

	fc_values[0] = CStringGetTextDatum(fc_xlogfilename);
	fc_isnull[0] = false;

	/*
	 * offset
	 */
	fc_xrecoff = XLogSegmentOffset(fc_locationpoint, wal_segment_size);

	fc_values[1] = UInt32GetDatum(fc_xrecoff);
	fc_isnull[1] = false;

	/*
	 * 元组压缩：首先准备您的数据项，然后合并在一起
	 */
	fc_resultHeapTuple = heap_form_tuple(fc_resultTupleDesc, fc_values, fc_isnull);

	fc_result = HeapTupleGetDatum(fc_resultHeapTuple);

	PG_RETURN_DATUM(fc_result);
}

/*
 * 根据WAL位置计算xlog文件名，
 * 例如由pg_backup_stop()或pg_switch_wal()返回的。
 */
Datum pg_walfile_name(PG_FUNCTION_ARGS)
{
	XLogSegNo	fc_xlogsegno;
	XLogRecPtr	fc_locationpoint = PG_GETARG_LSN(0);
	char		fc_xlogfilename[MAXFNAMELEN];

	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is in progress"),
				 errhint("%s cannot be executed during recovery.",
						 "pg_walfile_name()")));

	XLByteToPrevSeg(fc_locationpoint, fc_xlogsegno, wal_segment_size);
	XLogFileName(fc_xlogfilename, GetWALInsertionTimeLine(), fc_xlogsegno,
				 wal_segment_size);

	PG_RETURN_TEXT_P(cstring_to_text(fc_xlogfilename));
}

/*
 * pg_wal_replay_pause - 请求暂停恢复
 *
 * 对此函数的权限检查通过正常的
 * GRANT系统进行管理。
 */
Datum pg_wal_replay_pause(PG_FUNCTION_ARGS)
{
	if (!RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is not in progress"),
				 errhint("Recovery control functions can only be executed during recovery.")));

	if (PromoteIsTriggered())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("standby promotion is ongoing"),
				 errhint("%s cannot be executed after promotion is triggered.",
						 "pg_wal_replay_pause()")));

	SetRecoveryPause(true);

	/* 唤醒恢复进程，以便它可以处理暂停请求 */
	WakeupRecovery();

	PG_RETURN_VOID();
}

/*
 * pg_wal_replay_resume - 现在恢复
 *
 * 对此函数的权限检查通过正常的
 * GRANT系统进行管理。
 */
Datum pg_wal_replay_resume(PG_FUNCTION_ARGS)
{
	if (!RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is not in progress"),
				 errhint("Recovery control functions can only be executed during recovery.")));

	if (PromoteIsTriggered())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("standby promotion is ongoing"),
				 errhint("%s cannot be executed after promotion is triggered.",
						 "pg_wal_replay_resume()")));

	SetRecoveryPause(false);

	PG_RETURN_VOID();
}

/*
 * pg_is_wal_replay_paused
 */
Datum pg_is_wal_replay_paused(PG_FUNCTION_ARGS)
{
	if (!RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is not in progress"),
				 errhint("Recovery control functions can only be executed during recovery.")));

	PG_RETURN_BOOL(GetRecoveryPauseState() != RECOVERY_NOT_PAUSED);
}

/*
 * pg_get_wal_replay_pause_state - 返回恢复暂停状态。
 *
 * 返回的值：
 *
 * 'not paused' - 如果未请求暂停
 * 'pause requested' - 如果请求暂停但恢复尚未暂停
 * 'paused' - 如果恢复已暂停
 */
Datum pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS)
{
	char	   *fc_statestr = NULL;

	if (!RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is not in progress"),
				 errhint("Recovery control functions can only be executed during recovery.")));

	/* 获取恢复暂停状态 */
	switch (GetRecoveryPauseState())
	{
		case RECOVERY_NOT_PAUSED:
			fc_statestr = "not paused";
			break;
		case RECOVERY_PAUSE_REQUESTED:
			fc_statestr = "pause requested";
			break;
		case RECOVERY_PAUSED:
			fc_statestr = "paused";
			break;
	}

	Assert(fc_statestr != NULL);
	PG_RETURN_TEXT_P(cstring_to_text(fc_statestr));
}

/*
 * 返回最新处理的提交/撤销记录的时间戳。
 *
 * 当服务器正常启动而没有恢复时，
 * 该函数返回NULL。
 */
Datum pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS)
{
	TimestampTz fc_xtime;

	fc_xtime = GetLatestXTime();
	if (fc_xtime == 0)
		PG_RETURN_NULL();

	PG_RETURN_TIMESTAMPTZ(fc_xtime);
}

/*
 * 返回一个布尔值，表示当前恢复模式，这是一个全局状态。
 */
Datum pg_is_in_recovery(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(RecoveryInProgress());
}

/*
 * 计算两个WAL位置之间的字节差。
 */
Datum pg_wal_lsn_diff(PG_FUNCTION_ARGS)
{
	Datum		fc_result;

	fc_result = DirectFunctionCall2(pg_lsn_mi,
								 PG_GETARG_DATUM(0),
								 PG_GETARG_DATUM(1));

	PG_RETURN_NUMERIC(fc_result);
}

/*
 * 促进备用服务器。
 *
 * 结果为“true”表示如果“wait”为
 * “true”，则升级已完成；如果“wait”为false，则已启动。
 */
Datum pg_promote(PG_FUNCTION_ARGS)
{
	bool		fc_wait = PG_GETARG_BOOL(0);
	int			fc_wait_seconds = PG_GETARG_INT32(1);
	FILE	   *fc_promote_file;
	int			fc_i;

	if (!RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("recovery is not in progress"),
				 errhint("Recovery control functions can only be executed during recovery.")));

	if (fc_wait_seconds <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("\"wait_seconds\" must not be negative or zero")));

	/* 创建提升信号文件 */
	fc_promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w");
	if (!fc_promote_file)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m",
						PROMOTE_SIGNAL_FILE)));

	if (FreeFile(fc_promote_file))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m",
						PROMOTE_SIGNAL_FILE)));

	/* 发信号给postmaster */
	if (kill(PostmasterPid, SIGUSR1) != 0)
	{
		ereport(WARNING,
				(errmsg("failed to send signal to postmaster: %m")));
		(void) unlink(PROMOTE_SIGNAL_FILE);
		PG_RETURN_BOOL(false);
	}

	/* 如果未请求等待则立即返回 */
	if (!fc_wait)
		PG_RETURN_BOOL(true);

	/* 等待直到提升所需的时间 */
#define WAITS_PER_SECOND 10
	for (fc_i = 0; fc_i < WAITS_PER_SECOND * fc_wait_seconds; fc_i++)
	{
		int			fc_rc;

		ResetLatch(MyLatch);

		if (!RecoveryInProgress())
			PG_RETURN_BOOL(true);

		CHECK_FOR_INTERRUPTS();

		fc_rc = WaitLatch(MyLatch,
					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
					   1000L / WAITS_PER_SECOND,
					   WAIT_EVENT_PROMOTE);

		/*
		 * 如果postmaster已死则紧急退出。这是为了避免
		 * 手动清理所有postmaster子进程的必要性。
		 */
		if (fc_rc & WL_POSTMASTER_DEATH)
			PG_RETURN_BOOL(false);
	}

	ereport(WARNING,
			(errmsg_plural("server did not promote within %d second",
						   "server did not promote within %d seconds",
						   fc_wait_seconds,
						   fc_wait_seconds)));
	PG_RETURN_BOOL(false);
}
